/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package mathma.core.vector;

import mathma.core.expr.Expression;

import mathma.core.real.Real;

import java.util.ArrayList;
import java.util.List;

/**
 * An implementation of Vector using Reals.  A RealVector can contain as many
 * components as necessary, but must contain at least one.
 *
 * @since 0.2.6
 * @author Ethan Levine
 */
public final class RealVector extends Vector<Real> {

    /**
     * Creates a new RealVector and fills it with 0's.  This is generally used
     * as a base for repeated addition.
     *
     * @param length the length of the new vector.
     *
     * @return a RealVector containing all 0's.
     */
    public static RealVector createZeroVector(int length) {
        List<Expression<Real>> comps = new ArrayList<Expression<Real>>();
        for (int x = 0; x < length; x++) {
            comps.add(Real.ZERO);
        }
        return new RealVector(comps);
    }

    /**
     * Creates a new RealVector from a variable number of components.
     *
     * @param comps the components of the vector.
     */
    public RealVector(Expression<Real>... comps) {
        super(comps);
    }

    /**
     * Creates a new RealVector from a list of components.
     *
     * @param comps the components of the vector.
     */
    public RealVector(List<Expression<Real>> comps) {
        super(comps);
    }

    public RealVector add(Vector<Real> other) {
        if (other.getSize() == getSize()) {
            List<Expression<Real>> comps = new ArrayList<Expression<Real>>();
            int pos = 0;
            for (Expression<Real> otherComp : other) {
                comps.add(otherComp.evaluate().add(getComponent(pos).evaluate()));
                pos++;
            }
            return new RealVector(comps);
        } else {
            // TODO throw calculation exception - mismatching length
            throw new RuntimeException();
        }
    }

    public RealVector subtract(Vector<Real> other) {
        if (other.getSize() == getSize()) {
            List<Expression<Real>> comps = new ArrayList<Expression<Real>>();
            int pos = 0;
            for (Expression<Real> otherComp : other) {
                comps.add(getComponent(pos).evaluate().subtract(otherComp.evaluate()));
                pos++;
            }
            return new RealVector(comps);
        } else {
            // TODO throw calculation exception - mismatching length
            throw new RuntimeException();
        }
    }

    public RealVector scalarProduct(Real scalar) {
        List<Expression<Real>> comps = new ArrayList<Expression<Real>>();
        for (Expression<Real> comp : this) {
            comps.add(comp.evaluate().multiply(scalar));
        }
        return new RealVector(comps);
    }

    public Real dotProduct(Vector<Real> other) {
        if (other.getSize() == getSize()) {
            Real runningTotal = Real.ZERO;
            int pos = 0;
            for (Expression<Real> otherComp : other) {
                runningTotal.add(otherComp.evaluate().multiply(getComponent(pos).evaluate()));
                pos++;
            }
            return runningTotal;
        } else {
            // TODO throw calculation exception - mismatching length
            throw new RuntimeException();
        }
    }

    public Real magnitude() {
        Real runningTotal = Real.ZERO;
        for (Expression<Real> comp : this) {
            runningTotal.add(comp.evaluate().square());
        }
        return runningTotal.sqrt();
    }

    public RealVector crossProduct(Vector<Real> other) {
        // TODO implement cross product
        return null;
    }

    public Real angleWith(Vector<Real> other) {
        return dotProduct(other).divideBy(magnitude().multiply(other.magnitude())).invCosine();
    }

    public RealVector unitVector() {
        List<Expression<Real>> comps = new ArrayList<Expression<Real>>();
        Real mag = magnitude();
        for (Expression<Real> comp : this) {
            comps.add(comp.evaluate().divideBy(mag));
        }
        return new RealVector(comps);
    }

    public boolean isPerpendicularTo(Vector<Real> other) {
        return dotProduct(other).equals(Real.ZERO);
    }

    public boolean isParallelTo(Vector<Real> other) {
        return unitVector().equals(other.unitVector());
    }

    public boolean isAntiparallelTo(Vector<Real> other) {
        return unitVector().equals(other.unitVector().scalarProduct(Real.NEGATIVE_ONE));
    }

    public Vector<Real> projectionAlong(Vector<Real> other) {
        return other.scalarProduct(dotProduct(other).divideBy(other.magnitude().square()));
    }
}
